using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using MacomberMap.Common.Components;
using MacomberMap.Common.Events;
using MacomberMap.Common.User_Interfaces.Components;
using MacomberMap.Common.Logging;
using MacomberMap.Common.Internals;
using System.IO;
using System.Xml;
using System.Reflection;
using MacomberMap.Common.Database_Interface;

namespace MacomberMap.Common.User_Interfaces.Events
{
    /// <summary>
    /// This class provides the event viewer for system events
    /// </summary>
    public partial class MM_Event_Viewer : UserControl
    {

        #region Variable declarations

        /// <summary>Our collection of events shown within this display</summary>
        public Dictionary<MM_Event_Type, List<MM_Event>> EventCollection = new Dictionary<MM_Event_Type, List<MM_Event>>(MM_Overall_Display.EventTypes.Count);

        /// <summary>This delegate determines whether the event should be displayed</summary>
        /// <param name="Event">The event to check against</param>
        public delegate bool ValidityCheckerDelegate(MM_Event Event);

        /// <summary>The function that determines whether the element should be displayed</summary>
        public ValidityCheckerDelegate ValidityChecker;

        /// <summary>The menu strip for right-click handling</summary>
        private ContextMenuStrip cms = new ContextMenuStrip();

        /// <summary>The collection of connected databases with which the event viewer is linked</summary>
        private MM_Database[] ConnectedDatabases;

        /// <summary>Our cached list of elements</summary>
        private List<MM_Event> EventCache = new List<MM_Event>();

        /// <summary>Our cached list of lines</summary>
        private List<ListViewItem> ItemCache = new List<ListViewItem>();

        /// <summary>The top index of the item cache</summary>
        private int TopIndex;

        /// <summary>The bottom index of the item cache</summary>
        private int BottomIndex;

        /// <summary>A single cached listview item, to assist in sizing issues.</summary>
        private ListViewItem SizingHelper;

        /// <summary>Our collection of event types</summary>
        private bool[] EventStates;
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize the event viewer
        /// </summary>        
        public MM_Event_Viewer()
        {
            InitializeComponent();
            lvEvents.View = View.Details;
            lvEvents.SmallImageList = MM_Overall_Display.EventImages;
            lvEvents.Columns.Clear();
            lvEvents.Columns.Add("Event");
            lvEvents.Columns.Add("State");
            lvEvents.Columns.Add("Time");
            lvEvents.VirtualMode = true;
            lvEvents.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(lvEvents_RetrieveVirtualItem);
            lvEvents.CacheVirtualItems += new CacheVirtualItemsEventHandler(lvEvents_CacheVirtualItems);
            lvEvents.SelectedIndexChanged += new EventHandler(lvEvents_SelectedIndexChanged);
            SizingHelper = new ListViewItem();
            SizingHelper.SubItems.Add("");
            SizingHelper.SubItems.Add("");     
       
            //Initialize our event types and our corresponding menus
            EventStates = new bool[Enum.GetValues(typeof(MM_Event.EventStateEnum)).Length];
            for (int a = 0; a < EventStates.Length; a++)
                EventStates[a] = true;
            foreach (String str in Enum.GetNames(typeof(MM_Event.EventStateEnum)))            
            {
                ToolStripMenuItem EnumName = btnStates.DropDownItems.Add(str) as ToolStripMenuItem;
                EnumName.Checked = true;
                EnumName.CheckOnClick = true;
                EnumName.CheckedChanged += new EventHandler(EnumName_CheckedChanged);                
                EnumName.Tag = Enum.Parse(typeof(MM_Event.EventStateEnum), str);   
                 
            }            
        }

        /// <summary>
        /// Update the background color
        /// </summary>
        /// <param name="e"></param>
        protected override void OnBackColorChanged(EventArgs e)
        {
            this.lvEvents.BackColor = this.BackColor;
            base.OnBackColorChanged(e);
        }

        /// <summary>
        /// Update the foreground color
        /// </summary>
        /// <param name="e"></param>
        protected override void OnForeColorChanged(EventArgs e)
        {
            this.lvEvents.ForeColor = this.ForeColor;
        }

        /// <summary>
        /// Handle turning on or off items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void EnumName_CheckedChanged(object sender, EventArgs e)
        {
            ToolStripMenuItem Sender = (ToolStripMenuItem)sender;
            EventStates[(int)Sender.Tag] = Sender.Checked;
            foreach (KeyValuePair<MM_Event_Type, List<MM_Event>> kvp in EventCollection)
                UpdateToolStripMenu(tsMain.Items[kvp.Key.Name]);
        }

        /// <summary>
        /// Update a tool strip menu by scanning through its items and adding in as appropriate
        /// </summary>
        /// <param name="tsItem">The item to be modified</param>
        private void UpdateToolStripMenu(ToolStripItem tsItem)
        {
            //Go through the event type, and tally all counts by state and total visible
            MM_Event_Type BaseType = (MM_Event_Type)tsItem.Tag;
            int[] CountStates = new int[Enum.GetValues(typeof(MM_Event.EventStateEnum)).Length];
            int CountVisible=0;
            foreach (MM_Event Event in EventCollection[BaseType])
            {
                CountStates[(int)Event.EventState]++;
                if (EventStates[(int)Event.EventState])
                    CountVisible++;
            }

            //Now, write out our tally to the tool tip
            tsItem.Text = CountVisible.ToString("#,##0");
            StringBuilder OutText = new StringBuilder(BaseType.Name);
            for (int a = 0; a < CountStates.Length; a++)
                OutText.Append("\n" + Enum.GetName(typeof(MM_Event.EventStateEnum), a) + ": " + CountStates[a].ToString("#,##0"));
            tsItem.ToolTipText = OutText.ToString();            
        }





        /// <summary>
        /// Assign data connectors to the event viewer
        /// </summary>
        /// <param name="ConnectedDatabases">The databases to which the event viewer is connected</param>
        public void AssignConnectors(params MM_Database[] ConnectedDatabases)
        {
            this.ConnectedDatabases = ConnectedDatabases;
        }
        #endregion

        #region Event handler interfaces
        /// <summary>
        /// Assign event handlers for this list view
        /// </summary>
        public void AssignEventHandlers(ValidityCheckerDelegate ValidityChecker)
        {
            lock (this)
            {
                this.ValidityChecker = ValidityChecker;
                MM_Log.EventAdded += new MM_Log.MM_Event_Delegate(MM_Log_EventAdded);
                MM_Log.EventRemoved += new MM_Log.MM_Event_Delegate(MM_Log_EventRemoved);
                MM_Log.EventUpdated += new MM_Log.MM_Event_Delegate(MM_Log_EventUpdated);

                //Now, go through all existing events, and handle.
                foreach (MM_Event Event in MM_Repository.Events.Values)
                    AddEvent(Event);
                    //MM_Log_EventAdded(Event);
            }
        }

        /// <summary>
        /// Assign event handlers for this list view
        /// </summary>
        public void RemoveEventHandlers()
        {            
            MM_Log.EventAdded -= MM_Log_EventAdded;
            MM_Log.EventRemoved -= MM_Log_EventRemoved;
            MM_Log.EventUpdated -= MM_Log_EventUpdated;
        }

        /// <summary>
        /// Handle an event being updated
        /// </summary>        
        /// <param name="Event"></param>
        private void MM_Log_EventUpdated(MM_Event Event)
        {
            if ((tsMain.Items[Event.EventType.Name] as ToolStripButton).Checked)
                UpdateEvent(Event);            
        }


        /// <summary>
        /// Handle an event's removal
        /// </summary>
        /// <param name="Event"></param>
        private void MM_Log_EventRemoved(MM_Event Event)
        {
            lock (this)
            {
                if (EventStates[(int)Event.EventState])
                {
                    EventCache.Remove(Event);
                    lvEvents.VirtualListSize = EventCache.Count;
                }
                EventCollection[Event.EventType].Remove(Event);
                UpdateToolStripMenu(tsMain.Items[Event.EventType.Name]);
            }   
        }

        /// <summary>
        /// Handle an event being added
        /// </summary>
        /// <param name="Event"></param>
        private void MM_Log_EventAdded(MM_Event Event)
        {
            if (ValidityChecker(Event))
                AddEvent(Event);
        }

        /// <summary>
        /// Return true to all events, so that all events are reflected within the specified event viewer
        /// </summary>        
        /// <param name="Event"></param>
        /// <returns></returns>
        public static bool IncludeAllEvents(MM_Event Event)
        {
            return true;
        }
        #endregion

        #region ListView interfaces

        /// <summary>
        /// Add a new event to the list
        /// </summary>
        /// <param name="Event"></param>
        private delegate void SafeAddElement(MM_Event Event);


        /// <summary>
        /// Add a new event to the list
        /// </summary>
        /// <param name="Event"></param>
        private void AddEvent(MM_Event Event)
        {
            if (InvokeRequired)
                this.BeginInvoke(new SafeAddElement(AddEvent), Event);
            else
                try
                {
                    ListViewItem lvI = Event.BuildListViewItem();
                    lvI.Tag = Event;

                    List<MM_Event> dict;
                    if (!EventCollection.TryGetValue(Event.EventType, out dict))
                        EventCollection.Add(Event.EventType, (dict = new List<MM_Event>()));
                    dict.Add(Event);

                    if (!tsMain.Items.ContainsKey(Event.EventType.Name))
                    {
                        ToolStripButton tsl = new ToolStripButton("0");
                        tsl.Name = Event.EventType.Name;
                        tsl.Image = Event.EventType.EventImage;
                        tsl.ImageAlign = ContentAlignment.MiddleLeft;
                        tsl.TextAlign = ContentAlignment.MiddleRight;
                        tsl.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
                        tsl.ToolTipText = Event.EventType.Name;
                        tsl.Tag = Event.EventType;
                        tsl.CheckOnClick = true;
                        tsl.Checked = (Event.EventState == MM_Event.EventStateEnum.New);
                        tsl.CheckedChanged += new EventHandler(tsl_CheckedChanged);
                        for (int a = 1; a < tsMain.Items.Count - 2; a++)
                            if (String.Compare(tsl.ToolTipText, tsMain.Items[a].ToolTipText, true) == -1)
                            {
                                tsMain.Items.Insert(a, tsl);
                                break;
                            }
                        if (!tsMain.Items.Contains(tsl))
                            if (tsMain.Items.Count == 0)
                                tsMain.Items.Add(tsl);
                            else
                                tsMain.Items.Insert(tsMain.Items.Count - 1, tsl);
                        tsl.Image = Event.EventType.EventImage;

                    }
                    else
                        tsMain.Items[Event.EventType.Name].Text = EventCollection[Event.EventType].Count.ToString("#,##0");

                    if ((tsMain.Items[Event.EventType.Name] as ToolStripButton).Checked && EventStates[(int)Event.EventState])
                        lock (this)
                        {
                            EventCache.Add(Event);
                            lvEvents.VirtualListSize++;
                        }

                    UpdateToolStripMenu(tsMain.Items[Event.EventType.Name]);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error adding event: " + ex.Message);
                }
        }

        /// <summary>
        /// Show/hide elements of the specified type
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tsl_CheckedChanged(object sender, EventArgs e)
        {
            ToolStripButton Sender = (ToolStripButton)sender;
            lock (this)
            {
                if (Sender.Checked)
                {
                    List<MM_Event> EventsToAdd = new List<MM_Event>();
                    foreach (MM_Event ThisEvent in EventCollection[Sender.Tag as MM_Event_Type])
                        if (EventStates[(int)ThisEvent.EventState])
                            EventsToAdd.Add(ThisEvent);
                    EventCache.AddRange(EventsToAdd);                    
                    lvEvents.VirtualListSize = EventCache.Count;
                }
                else
                {
                    foreach (MM_Event EventToRemove in EventCollection[Sender.Tag as MM_Event_Type])
                        if (EventStates[(int)EventToRemove.EventState])
                            EventCache.Remove(EventToRemove);
                    lvEvents.VirtualListSize = EventCache.Count;
                }
            }
        }

        /// <summary>
        /// Update an already existing event
        /// </summary>
        /// <param name="Event">The event providing the updates</param>        
        private void UpdateEvent(MM_Event Event)
        {
            
        }
        #endregion

        #region Export event log
        /// <summary>
        /// Export a log file to a specified file
        /// </summary>
        /// <param name="FileName"></param>
        public void ExportLog(string FileName)
        {

            if (Path.GetExtension(FileName).ToLower() == ".log")
            {
                StreamWriter sW = new StreamWriter(FileName);
                foreach (ColumnHeader col in lvEvents.Columns)
                    sW.Write(col.Text + ",");
                sW.WriteLine();

                foreach (MM_Event Event in EventCache)
                {
                    ListViewItem lvI = Event.BuildListViewItem();
                    for (int a = 0; a < lvI.SubItems.Count; a++)
                        sW.Write(lvI.SubItems[a].Text + ",");
                    sW.WriteLine();
                }
                sW.Close();
            }
            else if (Path.GetExtension(FileName).ToLower() == ".xml")
                using (XmlTextWriter xW = new XmlTextWriter(FileName, Encoding.UTF8))
                {
                    xW.Formatting = Formatting.Indented;
                    xW.WriteStartDocument(true);
                    xW.WriteStartElement("CRR_NetworkMap_Merge");
                    xW.WriteAttributeString("LogExported", XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Unspecified));

                    //Now, go through all elements, and export them.                    
                    foreach (ListViewItem lvI in lvEvents.Items)
                        using (Control c = new Control())
                        {
                            MM_Event ThisEvent = (lvI.Tag as MM_Event);
                            xW.WriteStartElement(ThisEvent.GetType().Name);
                            xW.WriteAttributeString("Value", lvI.SubItems[0].Text);
                            ThisEvent.DisplayDetails(c);
                            if (c.Controls.Count > 0)
                                foreach (TreeNode tN in (c.Controls[0] as TreeView).Nodes)
                                    WriteTreeNode(xW, tN);
                            xW.WriteEndElement();
                        }
                    xW.WriteEndDocument();
                    xW.Close();
                }
        }

        /// <summary>
        /// Write out a treeview node
        /// </summary>
        /// <param name="xW">The XML text writer</param>
        /// <param name="tN">The tree node to be written</param>
        private void WriteTreeNode(XmlTextWriter xW, TreeNode tN)
        {
            xW.WriteStartElement("Entry");
            if (tN.Text.Contains(":"))
                xW.WriteAttributeString(tN.Text.Split(':')[0], tN.Text.Split(':')[1]);
            else
                xW.WriteAttributeString("Value", tN.Text);

            foreach (TreeNode tN2 in tN.Nodes)
                WriteTreeNode(xW, tN2);

            xW.WriteEndElement();
        }
        #endregion

        #region ListView events
        /// <summary>
        /// Handle a change in ListView.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvEvents_SelectedIndexChanged(object sender, EventArgs e)
        {
            lock (this)
            {
                foreach (Control ctl in splMain.Panel2.Controls)
                    ctl.Dispose();
                splMain.Panel2.Controls.Clear();
                if (lvEvents.SelectedIndices.Count == 1)
                {
                    MM_Event FoundEvent = (lvEvents.Items[lvEvents.SelectedIndices[0]].Tag as MM_Event);
                    if (FoundEvent != null)
                        FoundEvent.DisplayDetails(splMain.Panel2);
                }
            }
        }
        #endregion

        #region Virtual ListView events
        /// <summary>
        /// Retrieve an item for display to the virtual mode
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvEvents_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            if (e.ItemIndex < 0 || TopIndex < 0)
                e.Item = null;

            if (e.ItemIndex - TopIndex >= ItemCache.Count || e.ItemIndex - TopIndex < 0)
            {
                EventCache[e.ItemIndex].UpdateListViewItem(SizingHelper);
                e.Item = SizingHelper;
            }
            else
            {
                ListViewItem lvI = ItemCache[e.ItemIndex - TopIndex];
                EventCache[e.ItemIndex].UpdateListViewItem(lvI);
                lvI.Tag = EventCache[e.ItemIndex];
                e.Item = lvI;
            }
        }

        /// <summary>
        /// Handle the caching of virtual items by ensuring the appropriate sizing and localization.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvEvents_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
        {
            //Determine our new indeces
            TopIndex = e.StartIndex;
            BottomIndex = e.EndIndex;

            //Add any new list view items as necessary
            int NumToAdd = (BottomIndex - TopIndex + 2) - ItemCache.Count;
            if (NumToAdd> 0)
                for (int a = 0; a < NumToAdd; a++)
                {
                    ListViewItem NewItem = new ListViewItem();
                    NewItem.SubItems.Add("");
                    NewItem.SubItems.Add("");
                    NewItem.SubItems.Add("");
                    ItemCache.Add(NewItem);
                }            
        }
        #endregion


        /// <summary>
        /// Handle the right-most button (show/hide details)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRight_Click(object sender, EventArgs e)
        {
            btnRight.Text = (splMain.Panel2Collapsed ^= true) ? "3" : "4";
            btnRight.ToolTipText = (splMain.Panel2Collapsed ? "<- Show Details" : "Hide details ->");
        }

        /// <summary>
        /// Update all items
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tmrUpdate_Tick(object sender, EventArgs e)
        {
            try
            {
                if (MM_Overall_Display.DeltaTime)
                    lock (this)
                        foreach (ListViewItem lvI in lvEvents.Items)
                            (lvI.Tag as MM_Event).UpdateTime(lvI);
            }
            catch (Exception ex)
            {
                File.WriteAllText("MMLoader-TickUpdateError-" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".log", ex.ToString());
            }
        }

        /// <summary>
        /// Handle the right-click on an event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvEvents_MouseClick(object sender, MouseEventArgs e)
        {
            ListViewHitTestInfo ht = lvEvents.HitTest(e.Location);
            if (ht.Item != null && e.Button == MouseButtons.Right)
                if (lvEvents.SelectedIndices.Count == 1)
                {
                    cms.Tag = ht.Item.Tag;
                    cms.Items.Clear();
                    MM_Event AssocEvent = (MM_Event)ht.Item.Tag;
                    (cms.Items.Add(AssocEvent.GetType().Name.Substring(9)) as ToolStripMenuItem).Enabled = false;
                    (cms.Items.Add("State: " + AssocEvent.EventState.ToString()) as ToolStripMenuItem).Enabled = false;
                    cms.Items.Add("-");
                    AssocEvent.AddMenuItems(cms);
                    cms.Items.Add("-");

                    MethodInfo FoundMethod = AssocEvent.GetType().GetMethod("ApproveChange");
                    if (FoundMethod != null)
                        (cms.Items.Add("Approve change") as ToolStripMenuItem).Click += ApproveChange;

                    cms.Show(Cursor.Position);
                }
                else if (lvEvents.SelectedIndices.Count > 1)
                {

                    MM_Event[] Events = new MM_Event[lvEvents.SelectedIndices.Count];
                    int a = 0;
                    int CanApprove = 0;
                    foreach (int SelectedIndex in lvEvents.SelectedIndices)
                    {
                        Events[a] = EventCache[SelectedIndex];
                        if (Events[a].GetType().GetMember("ApproveChange") != null)
                            CanApprove++;
                        a++;
                    }

                    cms.Items.Clear();
                    cms.Tag = Events;
                    (cms.Items.Add(Events.Length.ToString("#,##0") + " events") as ToolStripMenuItem).Enabled = false;
                    cms.Items.Add("-");
                    if (CanApprove > 0)
                        (cms.Items.Add("Approve " + CanApprove.ToString("#,##0") + " changes") as ToolStripMenuItem).Click += ApproveChange;
                    cms.Show(Cursor.Position);                    
                }
        }

        /// <summary>
        /// Take ownership of an item 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TakeOwnership(object sender, EventArgs e)
        {
            Object InObj = (sender as ToolStripMenuItem).Owner.Tag;
            MM_Event[] EventsToProcess = null;
            if (InObj is MM_Event)
                EventsToProcess = new MM_Event[] { InObj as MM_Event };
            else if (InObj is MM_Event[])
                EventsToProcess = InObj as MM_Event[];

            foreach (MM_Event EventToProcess in EventsToProcess)
                MM_Log.UpdateEventState(EventToProcess, EventToProcess.EventState, MM_Event.EventStateEnum.Owned);

        }


        /// <summary>
        /// Approve the changes to the model, by calling the event's ApproveChange model.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ApproveChange(object sender, EventArgs e)
        {
            Object InObj = (sender as ToolStripMenuItem).Owner.Tag;
            MM_Event[] EventsToProcess = null;
            if (InObj is MM_Event)
                EventsToProcess = new MM_Event[] { InObj as MM_Event };
            else if (InObj is MM_Event[])
                EventsToProcess = InObj as MM_Event[];

            foreach (MM_Event FoundEvent in EventsToProcess)
            {
                try
                {                    
                    MethodInfo mI = FoundEvent.GetType().GetMethod("ApproveChange");
                    if (mI != null)
                        mI.Invoke(FoundEvent, new object[] { ConnectedDatabases });
                    MM_Log.RemoveEvent(FoundEvent);
                    Application.DoEvents();

                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error approving change for " + FoundEvent.ToString() + ": " + ex.InnerException.Message + "\n" + ex.InnerException.StackTrace, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        /// <summary>
        /// Change the sorting of the columns
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvEvents_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            EventCache.Sort(new MM_EventSorter(e.Column));
            lvEvents.Invalidate();

        }

        private class MM_EventSorter : IComparer<MM_Event>
        {
            #region IComparer<MM_Event> Members
            private int ColumnNumber;
            public MM_EventSorter(int ColumnNumber)
            {
                this.ColumnNumber = ColumnNumber;
            }

            /// <summary>
            /// Compare two elements
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <returns></returns>
            public int Compare(MM_Event x, MM_Event y)
            {
                if (this.ColumnNumber == 0 && x.GetType().Name == y.GetType().Name)
                    return x.CompareTo(y);
                else if (this.ColumnNumber == 0)
                    return x.GetType().Name.Substring(9).CompareTo(y.GetType().Name.Substring(9));
                else if (this.ColumnNumber == 1)
                    return x.EventState.CompareTo(y.EventState);
                else if (this.ColumnNumber == 2)
                    return y.EventTime.CompareTo(x.EventTime);
                throw new InvalidOperationException("Invalid column number selected!");
            }

            #endregion
        }

    }
}
